#include <igraph.h>
#include "generater.h"

typedef struct edge {
    size_t from;
    size_t to;
} edge;

typedef struct DefaultGraphGenerater {
    GHashTable *vex_tab; 
    GHashTable *vex_type;
    size_t vex_num;
    GHashTable *write_file_tab;
    GHashTable *edge_tab;
    GHashTable *res2pid_tab;
    size_t edge_num;
} DefaultGraphGenerater;


guint edge_hash (gconstpointer k) {
    const edge* e;
    e = k;
    return e->from << 16 + e->to;
}

gboolean edge_equal (gconstpointer v1, gconstpointer v2) {
    const edge* e1, *e2;
    e1 = v1;
    e2 = v2;
    if (e1->from == e2->from && e1->to == e2->to)
        return TRUE;
    return FALSE;
}

int
generater_create (DefaultGraphGenerater ** degen) {
    *degen =  g_new0 (DefaultGraphGenerater, 1);
    (*degen)->vex_num = 1;
    (*degen)->edge_num = 1;
    (*degen)->vex_tab = g_hash_table_new (g_str_hash, g_str_equal);
    (*degen)->edge_tab = g_hash_table_new (edge_hash, edge_equal);
    (*degen)->vex_type = g_hash_table_new (g_direct_hash, g_direct_equal);
    (*degen)->write_file_tab = g_hash_table_new (g_str_hash, g_str_equal);
    (*degen)->res2pid_tab = g_hash_table_new (g_str_hash, g_str_equal);
    if (*degen == NULL)
        return -1;
    return 0;
}

int
default_graph_generater_process_log (void * opaque, LogElem *le) {
    DefaultGraphGenerater *degen = (DefaultGraphGenerater *) opaque;
    edge e, *tmp_e;
    gpointer tmp_v;
    gpointer v1, v2;
    guint i;
    GPtrArray *pid_arr;
    if (le->src == NULL || le->dest == NULL)
        return 0;
    switch (le->type) {
        case LE_FORK: {
            if ((v1 = g_hash_table_lookup (degen->vex_tab, le->src)) == NULL) {
                g_hash_table_insert (degen->vex_tab, g_strdup (le->src), (gpointer)(degen->vex_num));
                v1 = (gpointer)(degen->vex_num);
                g_hash_table_insert (degen->vex_type, (gpointer)(degen->vex_num), g_strdup ("pid"));
                degen->vex_num++;
            }
            if ((v2 = g_hash_table_lookup (degen->vex_tab, le->dest)) == NULL) {
                g_hash_table_insert (degen->vex_tab, g_strdup (le->dest), (gpointer)(degen->vex_num));
                v2 = (gpointer)(degen->vex_num);
                g_hash_table_insert (degen->vex_type, (gpointer)(degen->vex_num), g_strdup ("pid"));
                degen->vex_num++;
            }
            e.from = (size_t)v1;
            e.to = (size_t)v2;
            if (!g_hash_table_contains (degen->edge_tab, &e)) {
                tmp_e = g_new0 (edge, 1);
                tmp_e->from = (size_t)v1;
                tmp_e->to = (size_t)v2;
                g_hash_table_insert (degen->edge_tab, tmp_e, g_strdup ("fork"));
            }
            break;
        }
        case LE_READ: {
            v1 = g_hash_table_lookup (degen->vex_tab, le->src); // FILE
            v2 = g_hash_table_lookup (degen->vex_tab, le->dest); // PID
            if (g_hash_table_contains (degen->write_file_tab, le->src)) {
                pid_arr = g_hash_table_lookup (degen->write_file_tab, le->src);
                if (v1 == NULL) {
                    g_hash_table_insert (degen->vex_tab, g_strdup (le->src), (gpointer)(degen->vex_num));
                    v1 = (gpointer)(degen->vex_num);
                    degen->vex_num++;
                }
                for (i = 0; i < pid_arr->len; i++) {
                    tmp_v = g_hash_table_lookup (degen->vex_tab, pid_arr->pdata[i]);
                    if (tmp_v == NULL) {
                        g_hash_table_insert (degen->vex_tab, g_strdup ( pid_arr->pdata[i]), (gpointer)(degen->vex_num));
                        tmp_v = (gpointer)(degen->vex_num);
                        degen->vex_num++;
                    }
                    e.from = (size_t)tmp_v;
                    e.to = (size_t)v1;
                    if (!g_hash_table_contains (degen->edge_tab, &e)) {
                        tmp_e = g_new0 (edge, 1);
                        tmp_e->from = (size_t)tmp_v;
                        tmp_e->to = (size_t)v1;
                        g_hash_table_insert (degen->edge_tab, tmp_e, g_strdup ("write"));
                    }
                }
                v2 = g_hash_table_lookup (degen->vex_tab, le->dest); // PID
            }

            if (v1 != NULL) {
                if (v2 != NULL) {
                    e.from = (size_t)v1;
                    e.to = (size_t)v2;
                    if (!g_hash_table_contains (degen->edge_tab, &e)) {
                        tmp_e = g_new0 (edge, 1);
                        tmp_e->from = (size_t)v1;
                        tmp_e->to = (size_t)v2;
                        g_hash_table_insert (degen->edge_tab, tmp_e, g_strdup ("read"));
                    }
                } else { // v2 == NULL
                    g_hash_table_insert (degen->vex_tab, g_strdup (le->dest), (gpointer)(degen->vex_num));
                    v2 = (gpointer)(degen->vex_num);
                    degen->vex_num++;
                    e.from = (size_t)v1;
                    e.to = (size_t)v2;
                    if (!g_hash_table_contains (degen->edge_tab, &e)) {
                        tmp_e = g_new0 (edge, 1);
                        tmp_e->from = (size_t)v1;
                        tmp_e->to = (size_t)v2;
                        g_hash_table_insert (degen->edge_tab, tmp_e, g_strdup ("read"));
                    }
                }
            }
            break;
        }
        case LE_WRITE: {
            v1 = g_hash_table_lookup (degen->vex_tab, le->src); // PID
            v2 = g_hash_table_lookup (degen->vex_tab, le->dest); // FILE
            if (v1 != NULL && v2 != NULL) {
                e.from = (size_t)v1;
                e.to = (size_t)v2;
                if (!g_hash_table_contains (degen->edge_tab, &e)) {
                    tmp_e = g_new0 (edge, 1);
                    tmp_e->from = (size_t)v1;
                    tmp_e->to = (size_t)v2;
                    g_hash_table_insert (degen->edge_tab, tmp_e, g_strdup ("write"));
                }
            } else if (v1 == NULL && v2 != NULL) {
                g_hash_table_insert (degen->vex_tab, g_strdup (le->src), (gpointer)(degen->vex_num));
                v1 =  (gpointer)(degen->vex_num);
                degen->vex_num++;
                e.from = (size_t)v1;
                e.to = (size_t)v2;
                if (!g_hash_table_contains (degen->edge_tab, &e)) {
                    tmp_e = g_new0 (edge, 1);
                    tmp_e->from = (size_t)v1;
                    tmp_e->to = (size_t)v2;
                    g_hash_table_insert (degen->edge_tab, tmp_e, g_strdup ("write"));
                }
            } else if (v2 == NULL) {
                pid_arr = g_hash_table_lookup (degen->write_file_tab, le->dest);
                if (pid_arr != NULL) {
                    g_ptr_array_add (pid_arr, g_strdup (le->src));
                } else {
                    pid_arr = g_ptr_array_new ();
                    g_ptr_array_add (pid_arr, g_strdup (le->src));
                    g_hash_table_insert (degen->write_file_tab, strdup(le->dest), pid_arr);
                }
            }
            break;
        }
        case LE_EXEC: {
            // if ((v1 = g_hash_table_lookup (degen->vex_tab, le->src)) == NULL) {
            //     g_hash_table_insert (degen->vex_tab, g_strdup (le->src), (gpointer)(degen->vex_num));
            //     v1 = (gpointer)(degen->vex_num);
            //     g_hash_table_insert (degen->vex_type, (gpointer)(degen->vex_num), g_strdup ("file"));
            //     degen->vex_num++;
            // }
            // //exit (0);
            // if ((v2 = g_hash_table_lookup (degen->vex_tab, le->dest)) == NULL) {
            //     g_hash_table_insert (degen->vex_tab, g_strdup (le->dest), (gpointer)(degen->vex_num));
            //     v2 = (gpointer)(degen->vex_num);
            //     g_hash_table_insert (degen->vex_type, (gpointer)(degen->vex_num), g_strdup ("pid"));
            //     degen->vex_num++;
            // }
            // e.from = (size_t)v1;
            // e.to = (size_t)v2;
            // if (!g_hash_table_contains (degen->edge_tab, &e)) {
            //     tmp_e = g_new0 (edge, 1);
            //     tmp_e->from = (size_t)v1;
            //     tmp_e->to = (size_t)v2;
            //     g_hash_table_insert (degen->edge_tab, tmp_e, g_strdup ("exec"));
            // }
            // break;
            v1 = g_hash_table_lookup (degen->vex_tab, le->src); // FILE
            v2 = g_hash_table_lookup (degen->vex_tab, le->dest); // PID
            if (g_hash_table_contains (degen->write_file_tab, le->src)) {
                pid_arr = g_hash_table_lookup (degen->write_file_tab, le->src);
                if (v1 == NULL) {
                    g_hash_table_insert (degen->vex_tab, g_strdup (le->src), (gpointer)(degen->vex_num));
                    v1 = (gpointer)(degen->vex_num);
                    degen->vex_num++;
                }
                for (i = 0; i < pid_arr->len; i++) {
                    tmp_v = g_hash_table_lookup (degen->vex_tab, pid_arr->pdata[i]);
                    if (tmp_v == NULL) {
                        g_hash_table_insert (degen->vex_tab, g_strdup ( pid_arr->pdata[i]), (gpointer)(degen->vex_num));
                        tmp_v = (gpointer)(degen->vex_num);
                        degen->vex_num++;
                    }
                    e.from = (size_t)tmp_v;
                    e.to = (size_t)v1;
                    if (!g_hash_table_contains (degen->edge_tab, &e)) {
                        tmp_e = g_new0 (edge, 1);
                        tmp_e->from = (size_t)tmp_v;
                        tmp_e->to = (size_t)v1;
                        g_hash_table_insert (degen->edge_tab, tmp_e, g_strdup ("write"));
                    }
                }
                v2 = g_hash_table_lookup (degen->vex_tab, le->dest); // PID
            }

            if (v1 != NULL) {
                if (v2 != NULL) {
                    e.from = (size_t)v1;
                    e.to = (size_t)v2;
                    if (!g_hash_table_contains (degen->edge_tab, &e)) {
                        tmp_e = g_new0 (edge, 1);
                        tmp_e->from = (size_t)v1;
                        tmp_e->to = (size_t)v2;
                        g_hash_table_insert (degen->edge_tab, tmp_e, g_strdup ("exec"));
                    }
                } else { // v2 == NULL
                    g_hash_table_insert (degen->vex_tab, g_strdup (le->dest), (gpointer)(degen->vex_num));
                    v2 = (gpointer)(degen->vex_num);
                    degen->vex_num++;
                    e.from = (size_t)v1;
                    e.to = (size_t)v2;
                    if (!g_hash_table_contains (degen->edge_tab, &e)) {
                        tmp_e = g_new0 (edge, 1);
                        tmp_e->from = (size_t)v1;
                        tmp_e->to = (size_t)v2;
                        g_hash_table_insert (degen->edge_tab, tmp_e, g_strdup ("exec"));
                    }
                }
            }
            break;
        }
        case LE_RECV: {
            gboolean is_ipc = FALSE;
            char *pid1 =  g_hash_table_lookup (degen->res2pid_tab, le->src); 
            char *pid2 =  g_hash_table_lookup (degen->res2pid_tab, le->dest); 
            if (pid1 == NULL && pid2 == NULL) {
                v1 = g_hash_table_lookup (degen->vex_tab, le->src); // PORT
                v2 = g_hash_table_lookup (degen->vex_tab, le->dest); // PORT
                if (v1 == NULL) {
                    g_hash_table_insert (degen->vex_tab, g_strdup(le->src), (gpointer)(degen->vex_num));
                    v1 = (gpointer)(degen->vex_num);
                    degen->vex_num++;
                }
                if (v2 == NULL) {
                    g_hash_table_insert (degen->vex_tab, g_strdup(le->dest), (gpointer)(degen->vex_num));
                    v2 = (gpointer)(degen->vex_num);
                    degen->vex_num++;
                }
            } else if (pid1 == NULL) {
                v1 = g_hash_table_lookup (degen->vex_tab, le->src); // PORT
                v2 = g_hash_table_lookup (degen->vex_tab, pid2); // PID
                if (v1 == NULL) {
                    g_hash_table_insert (degen->vex_tab, g_strdup(le->src), (gpointer)(degen->vex_num));
                    v1 = (gpointer)(degen->vex_num);
                    degen->vex_num++;
                }
                if (v2 == NULL) {
                    g_hash_table_insert (degen->vex_tab, g_strdup(pid2), (gpointer)(degen->vex_num));
                    v2 = (gpointer)(degen->vex_num);
                    degen->vex_num++;
                }
            } else if (pid2 == NULL) {
                v1 = g_hash_table_lookup (degen->vex_tab, pid1); // PID
                v2 = g_hash_table_lookup (degen->vex_tab, le->dest); // PORT
                if (v1 == NULL) {
                    g_hash_table_insert (degen->vex_tab, g_strdup(pid1), (gpointer)(degen->vex_num));
                    v1 = (gpointer)(degen->vex_num);
                    degen->vex_num++;
                }
                if (v2 == NULL) {
                    g_hash_table_insert (degen->vex_tab, g_strdup(le->dest), (gpointer)(degen->vex_num));
                    v2 = (gpointer)(degen->vex_num);
                    degen->vex_num++;
                }
            } else {
                v1 = g_hash_table_lookup (degen->vex_tab, pid1); // PID
                v2 = g_hash_table_lookup (degen->vex_tab, pid2); // PID
                if (v1 == NULL) {
                    g_hash_table_insert (degen->vex_tab, g_strdup(pid1), (gpointer)(degen->vex_num));
                    v1 = (gpointer)(degen->vex_num);
                    degen->vex_num++;
                }
                if (v2 == NULL) {
                    g_hash_table_insert (degen->vex_tab, g_strdup(pid2), (gpointer)(degen->vex_num));
                    v2 = (gpointer)(degen->vex_num);
                    degen->vex_num++;
                }
                is_ipc = TRUE;
            }
            e.from = (size_t)v1;
            e.to = (size_t)v2;
            if (!g_hash_table_contains (degen->edge_tab, &e)) {
                tmp_e = g_new0 (edge, 1);
                tmp_e->from = (size_t)v1;
                tmp_e->to = (size_t)v2;
                if (is_ipc)
                    g_hash_table_insert (degen->edge_tab, tmp_e, g_strdup ("ipc"));
                else
                    g_hash_table_insert (degen->edge_tab, tmp_e, g_strdup ("recv"));
            }
            break;
        }
        case LE_SEND: {
            gboolean is_ipc = FALSE;
            char *pid1 =  g_hash_table_lookup (degen->res2pid_tab, le->src); 
            char *pid2 =  g_hash_table_lookup (degen->res2pid_tab, le->dest); 
            if (pid1 == NULL && pid2 == NULL) {
                v1 = g_hash_table_lookup (degen->vex_tab, le->src); // PORT
                v2 = g_hash_table_lookup (degen->vex_tab, le->dest); // PORT
                if (v1 == NULL) {
                    g_hash_table_insert (degen->vex_tab, g_strdup(le->src), (gpointer)(degen->vex_num));
                    v1 = (gpointer)(degen->vex_num);
                    degen->vex_num++;
                }
                if (v2 == NULL) {
                    g_hash_table_insert (degen->vex_tab, g_strdup(le->dest), (gpointer)(degen->vex_num));
                    v2 = (gpointer)(degen->vex_num);
                    degen->vex_num++;
                }
            } else if (pid1 == NULL) {
                v1 = g_hash_table_lookup (degen->vex_tab, le->src); // PORT
                v2 = g_hash_table_lookup (degen->vex_tab, pid2); // PID
                if (v1 == NULL) {
                    g_hash_table_insert (degen->vex_tab, g_strdup(le->src), (gpointer)(degen->vex_num));
                    v1 = (gpointer)(degen->vex_num);
                    degen->vex_num++;
                }
                if (v2 == NULL) {
                    g_hash_table_insert (degen->vex_tab, g_strdup(pid2), (gpointer)(degen->vex_num));
                    v2 = (gpointer)(degen->vex_num);
                    degen->vex_num++;
                }
            } else if (pid2 == NULL) {
                v1 = g_hash_table_lookup (degen->vex_tab, pid1); // PID
                v2 = g_hash_table_lookup (degen->vex_tab, le->dest); // PORT
                if (v1 == NULL) {
                    g_hash_table_insert (degen->vex_tab, g_strdup(pid1), (gpointer)(degen->vex_num));
                    v1 = (gpointer)(degen->vex_num);
                    degen->vex_num++;
                }
                if (v2 == NULL) {
                    g_hash_table_insert (degen->vex_tab, g_strdup(le->dest), (gpointer)(degen->vex_num));
                    v2 = (gpointer)(degen->vex_num);
                    degen->vex_num++;
                }
            } else {
                v1 = g_hash_table_lookup (degen->vex_tab, pid1); // PID
                v2 = g_hash_table_lookup (degen->vex_tab, pid2); // PID
                if (v1 == NULL) {
                    g_hash_table_insert (degen->vex_tab, g_strdup(pid1), (gpointer)(degen->vex_num));
                    v1 = (gpointer)(degen->vex_num);
                    degen->vex_num++;
                }
                if (v2 == NULL) {
                    g_hash_table_insert (degen->vex_tab, g_strdup(pid2), (gpointer)(degen->vex_num));
                    v2 = (gpointer)(degen->vex_num);
                    degen->vex_num++;
                }
                is_ipc = TRUE;
            }
            e.from = (size_t)v1;
            e.to = (size_t)v2;
            if (!g_hash_table_contains (degen->edge_tab, &e)) {
                tmp_e = g_new0 (edge, 1);
                tmp_e->from = (size_t)v1;
                tmp_e->to = (size_t)v2;
                if (is_ipc)
                    g_hash_table_insert (degen->edge_tab, tmp_e, g_strdup ("ipc"));
                else
                    g_hash_table_insert (degen->edge_tab, tmp_e, g_strdup ("send"));
            }
            break;
        }
        case LE_REFER:{
            char *pid = g_hash_table_lookup (degen->res2pid_tab, le->src);
            if (pid == NULL) {
                g_hash_table_insert (degen->res2pid_tab, g_strdup (le->src), g_strdup (le->dest));
            }
            break;
        }

    }
    return 0;
}

typedef struct GenCtx {
    igraph_t g;
    size_t vertices;
    size_t edge_n;
} GenCtx;

void vex_traverse (gpointer key, gpointer value, gpointer user_data) {
    char* k = key;
    size_t v = (size_t) value;
    GenCtx *ctx = user_data;
    SETVAS (&(ctx->g), "label", v-1, k);
}

void vex_type_traverse (gpointer key, gpointer value, gpointer user_data) {
    size_t v = (size_t) key;
    gchar * lb = value;
    GenCtx *ctx = user_data;
    SETVAS (&(ctx->g), "type", v-1, lb);
}

void edge_traverse (gpointer key, gpointer value, gpointer user_data) {
    edge *e = key;
    gchar * lb = value;
    char namebuf [1024];
    igraph_vector_int_t *v = user_data;
    GenCtx *ctx = user_data;
    igraph_add_edge (&(ctx->g), e->from-1, e->to-1);
    snprintf (namebuf, sizeof (namebuf), "%ld. %s",ctx->edge_n, lb);
    SETEAS(&(ctx->g), "label",ctx->edge_n , namebuf);
    ctx->edge_n++;
}


int
default_graph_generater_generate (void *opaque, FILE *out){
    DefaultGraphGenerater *degen = (DefaultGraphGenerater *) opaque;
    igraph_vector_int_t vec;
    igraph_vector_int_init (&vec,1);
    GenCtx ctx;
    // igraph_vector_int_t edges;
    ctx.edge_n = 0;
    size_t vertices;
    igraph_set_attribute_table(&igraph_cattribute_table);
    igraph_empty(&(ctx.g), 0, IGRAPH_DIRECTED);
    vertices = degen->vex_num - 1;
    igraph_add_vertices (&(ctx.g), vertices, NULL);
    g_hash_table_foreach (degen->vex_tab, vex_traverse, &ctx);
    g_hash_table_foreach (degen->edge_tab, edge_traverse, &ctx);
    igraph_write_graph_dot(&(ctx.g), out);
    // igraph_write_graph_graphml (&(ctx.g), out, TRUE);
    // igraph_write_graph_edgelist(&(ctx.g), out);
    igraph_destroy(&(ctx.g));
    return 0;
}

int
default_graph_generater_destruct (void *opaque) {

}


int 
default_graph_generater_create (GraphGenerater **gen) {
    int stat;
    *gen = g_new0 (GraphGenerater, 1);
    DefaultGraphGenerater *degen;
    if ((*gen) == NULL) return GENERATER_ERR;
    stat = generater_create (&degen);
    if (stat != 0) {
        g_free (*gen);
        goto error;
    }
    (*gen)->opaque = degen;
    (*gen)->process_log = default_graph_generater_process_log;
    (*gen)->generate = default_graph_generater_generate;
    (*gen)->destruct = default_graph_generater_destruct;
    return GENERATER_OK;
    error:
    return GENERATER_ERR;
}